LiteLLM: un proxy para unificar proveedores de modelos

Router de fibra con cables conectados representando orquestación de tráfico entre proveedores

La primera integración con un LLM es sencilla: una clave, un SDK, tres líneas y un prompt. La segunda, seis meses después, ya no. Aparece un segundo proveedor porque Claude razona mejor en tareas largas, o porque conviene tener un modelo self-hosted para datos que no pueden salir del perímetro, o porque los embeddings multilingües de Cohere cuestan una fracción de los de OpenAI. En ese momento el código deja de ser limpio. Cada SDK tiene su propio cliente, formato de mensajes, semántica de streaming, errores y reglas de function calling. El equipo escribe adaptadores, y cada requisito transversal — rate limiting, observabilidad, budget por cliente, fallback si un proveedor falla — se implementa por duplicado o por triplicado.

El patrón que resuelve esto es viejo en infraestructura: un proxy. En lugar de que cada aplicación hable directamente con cada proveedor, todas hablan con un único servicio interno que habla por ellas con el exterior. LiteLLM es, a día de hoy, el proyecto open source más serio para hacerlo. Ofrece una API compatible con la de OpenAI sobre más de cien proveedores, se despliega como librería o como servidor HTTP, y trae de serie casi todo lo que acabarías escribiendo tú mismo.

Por qué proxiar

La pregunta no es trivial, porque todo proxy añade latencia, otra pieza que mantener y otro punto de fallo. La justificación debe ser concreta. Hay cuatro razones, y normalmente aparecen juntas.

La primera es la homogeneidad. Un solo cliente OpenAI-compatible en todas las aplicaciones, apuntando a un endpoint interno, sustituye a media docena de SDKs. Cambiar de modelo pasa a ser un campo de configuración, no un refactor. Migrar una app entera de GPT-4 a Claude 3 Opus se reduce a reapuntar un alias.

La segunda es el gobierno. En cuanto hay más de un equipo usando LLMs, alguien pregunta cuánto gasta cada uno, y a ser posible quiere limitarlo antes de que el recibo del mes siguiente sorprenda a finanzas. Un proxy central tiene claves virtuales por equipo, por usuario o por servicio, con presupuesto y caducidad. Las claves reales de los proveedores solo viven en un sitio.

La tercera es la resiliencia. Los proveedores de LLM caen, rate-limitean y degradan respuestas con una frecuencia mayor de lo que cabría esperar de servicios de su precio. Un proxy puede declarar fallbacks — si GPT-4 devuelve 429 o 5xx, reintentar en Claude 3 Sonnet; si Anthropic está saturado, caer al Mistral autoalojado — sin que las aplicaciones se enteren. Esto convierte incidentes de proveedor en degradaciones silenciosas, no en caídas de producto.

La cuarta es la observabilidad. Métricas de coste, latencia y tokens por modelo, por tenant y por ruta, emitidas desde un solo punto a Prometheus o Langfuse, evitan tener que instrumentar cada llamada en cada aplicación. También es el sitio natural donde insertar cache, redacción de PII, auditoría y compliance.

Librería o servidor

LiteLLM se puede usar de dos modos, y la decisión marca el resto. En modo librería se importa litellm.completion dentro del código de la aplicación y se disfruta de la API unificada sin desplegar nada nuevo. Es razonable para monolitos, prototipos o scripts puntuales, pero pierde casi todas las ventajas transversales: cada instancia de la app necesita las claves, cada equipo hace su propio rate limiting, cada servicio emite métricas a su manera.

En modo proxy se despliega un binario aparte — contenedor, pod, systemd unit — y las aplicaciones le hablan como si fuera OpenAI. Esta es la configuración por defecto para cualquier uso serio. Su coste es un hop de red interno de orden 5-20 ms, despreciable frente a los cientos o miles de milisegundos de una llamada a un LLM. Su beneficio es que concentra en un punto toda la lógica transversal.

Qué se declara en el proxy

La configuración típica es un YAML con tres bloques. El primero, model_list, mapea nombres lógicos como gpt-4, claude-3-sonnet o mistral-local a configuraciones concretas de proveedor: el prefijo openai/, anthropic/ u ollama/ identifica el backend, la clave se lee de variable de entorno, y el api_base puede apuntar a un Ollama interno. El segundo, router_settings, declara política de enrutamiento y fallbacks: una lista ordenada por modelo lógico indica a qué otros saltar si el primero falla, y una estrategia global como least-busy, lowest-cost o lowest-latency decide el tie-breaker cuando hay varios candidatos. El tercero, general_settings, fija la clave maestra con la que un administrador crea claves virtuales vía API, apunta a una Postgres para persistir presupuestos y uso, y opcionalmente conecta un Redis para cache de respuestas semánticamente equivalentes.

El fragmento mínimo — el único que merece ocupar espacio aquí — captura las tres piezas juntas:

model_list:
  - model_name: gpt-4
    litellm_params:
      model: openai/gpt-4
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude-3-sonnet
    litellm_params:
      model: anthropic/claude-3-sonnet-20240229
      api_key: os.environ/ANTHROPIC_API_KEY

router_settings:
  fallbacks:
    - gpt-4: ["claude-3-sonnet"]
  routing_strategy: least-busy

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  database_url: os.environ/DATABASE_URL

El resto de la superficie — budgets por clave, caching con TTL en Redis, tagging por entorno, integraciones con Langfuse o Helicone, métricas Prometheus — se describe en el mismo fichero con bloques análogos y se aplica sin tocar el código de las aplicaciones.

Lo que no hay que esperar

LiteLLM traduce entre APIs distintas, y la traducción no siempre es perfecta. Las funciones más específicas de cada proveedor — structured output con esquemas complejos, el function calling de OpenAI frente al tool use de Anthropic, los modos de reasoning de ciertos modelos — a veces no mapean 1:1. Vale la pena mirar el changelog antes de confiar un flujo crítico a una traducción no trivial. La latencia añadida es pequeña pero no nula, y para embeddings a volumen alto puede notarse más de lo esperado. El propio proxy es una pieza más que mantener, con su base de datos, sus actualizaciones y sus métricas. Y si el uso real es un único proveedor sin previsión de cambiar, la complejidad no se paga: una capa de abstracción casera en el backend basta.

Patrón que funciona

El despliegue que he visto estabilizarse en varios equipos es siempre parecido. Dos réplicas del proxy detrás de un service interno, Postgres compartida para keys y uso, Redis para cache semántica, claves virtuales por equipo o servicio con presupuesto mensual, fallbacks declarados para los dos o tres modelos críticos, scrape de Prometheus con labels model, tenant y route, y alertas sobre tasa de error por proveedor. Las aplicaciones ven un solo endpoint OpenAI-compatible y envían un header con su clave virtual; todo lo demás ocurre en el proxy.

Conclusión

Un proxy de LLMs no es una idea revolucionaria, es la misma capa de indirección que ya pusimos entre aplicaciones y bases de datos, entre aplicaciones y colas, entre aplicaciones e identidades. Tiene sentido por las mismas razones: aísla decisiones que cambian con frecuencia, concentra gobierno y observabilidad, y permite que la aplicación se desentienda de los detalles del proveedor. LiteLLM es hoy la implementación más completa en open source, suficientemente estable para producción y lo bastante flexible como para acomodar los cambios que sin duda llegarán en el stack de modelos a lo largo de los próximos trimestres. Si hay un solo proveedor y no se prevé otro, la pieza es prescindible. A partir del segundo modelo, dejar de escribir adaptadores y delegarlo en un proxy deja de ser una cuestión de estilo y pasa a ser higiene básica.

Entradas relacionadas